﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Thor.Domain;
using Thor.Domain.Configuration;

namespace Thor.Repository.Azure.Table
{
    /// <summary>
    /// A repository that stores calendar entities.
    /// </summary>
    public class CalendarRepository : TableRepositoryBase, ICalendarRepository
    {
        #region Fields

        private CalendarContext _calendarContext;
        private AppointmentContext _appointmentContext;

        #endregion

        #region Constructors

        public CalendarRepository(Dictionary<string, object> repositoryParameters)
            : base(repositoryParameters)
        {
            _calendarContext = new CalendarContext(StorageAccount.TableEndpoint.AbsoluteUri, StorageAccount.Credentials);
            _appointmentContext = new AppointmentContext(StorageAccount.TableEndpoint.AbsoluteUri, StorageAccount.Credentials);
        }

        #endregion

        #region ICalendarRepository Members

        /// <summary>
        /// Inserts a new calendar record into the repository.
        /// </summary>
        /// <param name="calendar"></param>
        public void Insert(Calendar calendar)
        {
            _appointmentContext.ClearCalendar(calendar.Id.ToString());
            _calendarContext.Insert(CreateRowFromCalendar(calendar));
            _appointmentContext.Insert(CreateRowsFromCalendar(calendar));
        }

        /// <summary>
        /// Updates a calendar record in the repository.
        /// </summary>
        /// <param name="calendar"></param>
        public void Update(Calendar calendar)
        {
            _appointmentContext.ClearCalendar(calendar.Id.ToString());
            _calendarContext.Update(CreateRowFromCalendar(calendar));
            _appointmentContext.Insert(CreateRowsFromCalendar(calendar));
        }

        /// <summary>
        /// Deletes a calendar entity from the repository.
        /// </summary>
        /// <param name="calendar"></param>
        public void Delete(Calendar calendar)
        {
            _appointmentContext.ClearCalendar(calendar.Id.ToString());
            _calendarContext.Delete(CreateRowFromCalendar(calendar));
        }

        /// <summary>
        /// Fetches all calendar entities from the repository.
        /// </summary>
        /// <returns></returns>
        public List<Calendar> Fetch()
        {
            List<CalendarRow> rows = _calendarContext.GetRows();
            List<Calendar> calendars = null;

            if (rows != null)
            {
                calendars = new List<Calendar>();

                foreach (CalendarRow row in rows)
                {
                    var calendar = CreateCalendarFromRow(row);
                    calendar.Appointments = CreateAppointmentsFromRows(_appointmentContext.GetRowsForCalendar(calendar.Id.ToString()));
                    calendars.Add(calendar);
                }
            }

            return calendars;
        }

        /// <summary>
        /// Fetches a calendar entity from the repository.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public Calendar Fetch(Guid id)
        {
            CalendarRow row = _calendarContext.GetRow(id);

            if (row != null)
            {
                var calendar = CreateCalendarFromRow(row);
                calendar.Appointments = CreateAppointmentsFromRows(_appointmentContext.GetRowsForCalendar(calendar.Id.ToString()));
                return calendar;
            }
            else
                return null;
        }

        /// <summary>
        /// Creates a row from the POCO object.
        /// </summary>
        /// <param name="calendar"></param>
        /// <returns></returns>
        private CalendarRow CreateRowFromCalendar(Calendar calendar)
        {
            return new CalendarRow()
            {
                ApplicationName = ApplicationName,
                CalendarEmail = calendar.CalendarEmail,
                PartitionKey = ApplicationName,
                RowKey = calendar.Id.ToString(),
                Alias = calendar.Alias,
                Id = calendar.Id,
                TimeZone = calendar.TimeZone,
                PublicCanBook = calendar.PublicCanBook,
                PublicCanView = calendar.PublicCanView,
                UseBookingAgent = calendar.UseBookingAgent,
                DisplaySubject = calendar.DisplaySubject,
                UseTimeframe = calendar.UseTimeframe,
                StartTime = calendar.StartTime,
                EndTime = calendar.EndTime
            };
        }

        /// <summary>
        /// Creates a POCO object from the row.
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        private Calendar CreateCalendarFromRow(CalendarRow row)
        {
            Calendar calendar = new Calendar();

            calendar.CalendarEmail = row.CalendarEmail;
            calendar.Alias = row.Alias;
            calendar.Id = row.Id;
            calendar.TimeZone = row.TimeZone;
            calendar.PublicCanBook = row.PublicCanBook;
            calendar.PublicCanView = row.PublicCanView;
            calendar.UseBookingAgent = row.UseBookingAgent;
            calendar.DisplaySubject = row.DisplaySubject;
            calendar.UseTimeframe = row.UseTimeframe;
            calendar.StartTime = row.StartTime;
            calendar.EndTime = row.EndTime;

            return calendar;
        }

        /// <summary>
        /// Creates a row from the POCO object.
        /// </summary>
        /// <param name="appointment"></param>
        /// <param name="calendarId"></param>
        /// <returns></returns>
        private AppointmentRow CreateRowFromAppointment(Appointment appointment, string calendarId)
        {
            return new AppointmentRow()
            {
                ApplicationName = ApplicationName,
                Id = appointment.Id,
                EndTime = XmlConvert.ToString(appointment.EndTime, XmlDateTimeSerializationMode.Utc),
                StartTime = XmlConvert.ToString(appointment.StartTime, XmlDateTimeSerializationMode.Utc),
                Subject = appointment.Subject,
                PartitionKey = ApplicationName,
                RowKey = appointment.Id.ToString(),
                CalendarId = calendarId
            };
        }

        /// <summary>
        /// Creates rows for all appointments on a calendar.
        /// </summary>
        /// <param name="calendar"></param>
        /// <returns></returns>
        private List<AppointmentRow> CreateRowsFromCalendar(Calendar calendar)
        {
            var rows = new List<AppointmentRow>();

            if (calendar.Appointments != null)
            {
                foreach (var appointment in calendar.Appointments)
                    rows.Add(CreateRowFromAppointment(appointment, calendar.Id.ToString()));
            }

            return rows;
        }

        /// <summary>
        /// Creates a POCO object from the row.
        /// </summary>
        /// <param name="row"></param>
        /// <returns></returns>
        private Appointment CreateAppointmentFromRow(AppointmentRow row)
        {
            return new Appointment()
            {
                EndTime = XmlConvert.ToDateTime(row.EndTime, XmlDateTimeSerializationMode.Utc),
                StartTime = XmlConvert.ToDateTime(row.StartTime, XmlDateTimeSerializationMode.Utc),
                Id = row.Id,
                Subject = row.Subject
            };
        }

        /// <summary>
        /// Creates appointments from a list of rows.
        /// </summary>
        /// <param name="rows"></param>
        /// <returns></returns>
        private List<Appointment> CreateAppointmentsFromRows(List<AppointmentRow> rows)
        {
            var appointments = new List<Appointment>();

            if (rows != null)
            {
                foreach (var row in rows)
                    appointments.Add(CreateAppointmentFromRow(row));
            }

            return appointments;
        }

        #endregion
    }
}